/**
 * File:   DHTNodeModel.java
 * Author: Gvennet
 */
package dht.node;

import dht.cleaners.NodeModelCleaner;
import dht.identifier.identifier.Identifier;

import java.net.InetAddress;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * todo write javadoc
 */
public final class DHTNodeModel
{

	private final List<DHTNodeRow> nodes;

	private final List<DHTNodeEventListener> listenerList;

	private final ReentrantLock lock;

	private final int MAX_SIZE;

	private final ScheduledThreadPoolExecutor scheduledExecutor;

	private final NodeModelCleaner cleaner;

	ScheduledFuture<?> cleanFuture;

	private int statistics = 0;

	public DHTNodeModel(int max_size, ScheduledThreadPoolExecutor scheduledExecutor, NodeModelCleaner cleaner)
	{
		nodes = new LinkedList<DHTNodeRow>();
		listenerList = new CopyOnWriteArrayList<DHTNodeEventListener>();
		lock = new ReentrantLock();
		MAX_SIZE = max_size;
		this.scheduledExecutor = scheduledExecutor;
		this.cleaner = cleaner;

	}

	public void put(DHTNodeRow nodeRow)
	{

		lock.lock();
		try
		{
			if (nodes.size() < MAX_SIZE)
			{
				nodes.add(nodeRow);
				fireRowInserted(nodeRow);
			}
		}
		finally
		{
			lock.unlock();
		}
	}

	public boolean update(Identifier id, InetAddress ia, int port)
	{
		boolean result = false;
		boolean label = true;

		lock.lock();
		try
		{
			DHTNodeRow tempRow = null;
			for (DHTNodeRow row : nodes)
			{
				tempRow = row;
				if (tempRow.getId().compareTo(id) == 0)
				{
					tempRow.setContactTime(System.currentTimeMillis());
					fireRowUpdated(tempRow);
					label = false;
					break;
				}
			}
			if (label && nodes.size() < MAX_SIZE)
			{
				tempRow = new DHTNodeRow(id, ia, port, System.currentTimeMillis());
				nodes.add(tempRow);
				fireRowInserted(tempRow);
				result = true;
			}

		}
		finally
		{
			lock.unlock();
		}

		return result;
	}


	public List<DHTNodeRow> get(final Identifier id)
	{

		LinkedList<DHTNodeRow> tempCopy;
		lock.lock();

		try
		{
			tempCopy = new LinkedList<DHTNodeRow>(nodes);
		}
		finally
		{
			lock.unlock();
		}


		Identifier[] identifiers = new Identifier[tempCopy.size()];
		for (int i = 0; i < nodes.size(); i++)
		{
			identifiers[i] = nodes.get(i).getId();
		}

		Arrays.sort(identifiers, new Comparator<Identifier>()
		{

			//@Override
			public int compare(Identifier o, Identifier o1)
			{
				return o.getDistance(id).compareTo(o1.getDistance(id));
			}
		});


		List<DHTNodeRow> filter = new LinkedList<DHTNodeRow>();

		int N = identifiers.length;

		for (int i = 0; i < N; i++)
		{
			for (DHTNodeRow row : tempCopy)
			{
				if (row.getId().compareTo(identifiers[i]) == 0)
				{
					filter.add(row);
				}
			}
		}
		return filter;

	}

	public boolean contains(Identifier id)
	{
		lock.lock();

		try
		{
			for (DHTNodeRow nodeRow : nodes)
			{
				if (nodeRow.getId().compareTo(id) == 0)
				{
					return true;
				}
			}
			return false;
		}
		finally
		{
			lock.unlock();
		}
	}

	public List<DHTNodeRow> getNodes()
	{
		lock.lock();
		try
		{
			return new LinkedList<DHTNodeRow>(nodes);
		}
		finally
		{
			lock.unlock();
		}
	}

	public int getMaxSize()
	{
		return MAX_SIZE;
	}

	public int getSize()
	{
		lock.lock();
		try
		{
			return nodes.size();
		}
		finally
		{
			lock.unlock();
		}
	}

	public InetAddress getInetAddress(int i)
	{
		lock.lock();
		try
		{
			return nodes.get(i).getInetAddress();
		}
		finally
		{
			lock.unlock();
		}
	}

	public int getPort(int i)
	{
		lock.lock();
		try
		{
			return nodes.get(i).getPort();
		}
		finally
		{
			lock.unlock();
		}
	}

	public void remove(int i)
	{
		lock.lock();
		try
		{
			DHTNodeRow tempRow = nodes.get(i);
			nodes.remove(i);
			fireRowRemoved(tempRow);
		}
		finally
		{
			lock.unlock();
		}
	}

	public void remove(DHTNodeContacts contacts)
	{
		lock.lock();
		try
		{
			for (DHTNodeRow node : nodes)
			{
				if (node.getInetAddress().equals(contacts.getInetAddress()) && node.getPort() == contacts.getPort())
				{
					nodes.remove(node);
					fireRowRemoved(node);
				}
			}
		}
		finally
		{
			lock.unlock();
		}
	}

	public boolean contains(DHTNodeContacts contacts)
	{
		lock.lock();
		try
		{
			for (DHTNodeRow node : nodes)
			{
				if (node.getInetAddress().equals(contacts.getInetAddress()) && node.getPort() == contacts.getPort())
				{
					return true;
				}
			}
		}
		finally
		{
			lock.unlock();
		}
		return false;
	}

	public void startStatisticsGathering(long period)
	{
		scheduledExecutor.scheduleWithFixedDelay(new StatisticsGathering(), 0, period, TimeUnit.SECONDS);
	}

	public int getStatistics()
	{
		return statistics;
	}

	public void cleaning()
	{
		cleanFuture = scheduledExecutor.scheduleWithFixedDelay(new Clean(), 0, 1, TimeUnit.MILLISECONDS);
	}

	public void stopCleaning()
	{
		cleanFuture.cancel(true);
	}

	public void addListener(DHTNodeEventListener listener)
	{
		listenerList.add(listener);
	}

	public void removeListener(DHTNodeEventListener listener)
	{
		listenerList.remove(listener);
	}


	private void fireRowInserted(DHTNodeRow nodeRow)
	{
		for (DHTNodeEventListener aListenerList : listenerList)
		{
			aListenerList.rowAdded(nodeRow);
		}
	}

	private void fireRowRemoved(DHTNodeRow nodeRow)
	{
		for (DHTNodeEventListener aListenerList : listenerList)
		{
			aListenerList.rowRemoved(nodeRow);
		}
	}

	private void fireRowUpdated(DHTNodeRow nodeRow)
	{
		for (DHTNodeEventListener aListenerList : listenerList)
		{
			aListenerList.rowUpdated(nodeRow);
		}
	}

	private final class StatisticsGathering implements Runnable
	{

		//@Override

		public void run()
		{
			statistics = nodes.size();
		}

	}

	private final class Clean implements Runnable
	{

		//@Override

		public void run()
		{
			cleaner.clean();
		}
	}

}
